package jp.co.nes.awssupport.cloudformation.generator;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jp.co.nes.awssupport.common.base.AbstractClient;
import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.utility.AppInfoUtil;
import jp.co.nes.awssupport.common.utility.YamlUtil;

/**
 * テンプレートジェネレータです。<br>
 * Yaml形式のファイルを読み込み、Cloudformationのテンプレートにして出力します。<br>
 * 出力する形式は、アプリケーション情報のoutputTemplateに設定した拡張子がjsonの場合、Json形式、
 * yamlの場合、Yaml形式になります。<br>
 * なお、Json形式で出力した場合は、変換前のファイルに存在したコメントはなくなり、改行なしの形式で出力されます。
 * 【注意事項】<br>
 * Cloudformationの制限(2017/12/07時点)として、リクエストするテンプレートの最大サイズは51200バイトという内容があります。<br>
 * 出力したファイルが51200バイトより大きなサイズになってしまった場合は、テンプレート分離、S3へのアップロード等、
 * 何らかの対応が必要になります。
 */
public class TemplateGenerator extends AbstractClient {

    /** ロガー */
    private static Logger logger = LoggerFactory.getLogger(TemplateGenerator.class);

    /**
     * メインメソッドです。
     *
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        TemplateGenerator generator = new TemplateGenerator();
        generator.execute(args);
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.common.base.AbstractClient#execute()
     */
    @Override
    protected void execute() {
        String category = "TemplateGenerator";
        String sourceTemplate = AppInfoUtil.getVal(category, "sourceTemplate");
        String outputTemplate = AppInfoUtil.getVal(category, "outputTemplate");
        String stepFunctionsRootDir = AppInfoUtil.getVal(category, "stepFunctionsRootDir");
        try (
                BufferedReader in = new BufferedReader(new FileReader(sourceTemplate));
                BufferedWriter out = new BufferedWriter(new FileWriter(outputTemplate))) {
            Map<String, List<String>> sfMap = loadStepFunctions(stepFunctionsRootDir);
            StringBuilder builder = new StringBuilder();
            while (true) {
                String line = in.readLine();
                if (line == null) {
                    break;
                }
                builder.append(replaceLine(sfMap, line));
            }

            String yaml = builder.toString();
            logger.debug(yaml);

            String outputValue = null;
            if (outputTemplate.endsWith(".json")) {
                // 出力されるJsonのサイズをなるべく小さいサイズにするため、インデントしない。
                outputValue = YamlUtil.convertYamlStringToJsonString(yaml, false);
            } else if (outputTemplate.endsWith(".yaml")) {
                outputValue = yaml;
            } else {
                throw new CommonException("outputTemplate extension is wrong");
            }
            logger.debug(outputValue);

            out.write(outputValue);
        } catch (IOException e) {
            throw new CommonException(e);
        }
    }

    /**
     * StepFunctionsの内容を読み込み、マップに設定して返却します。<br>
     * マップのキーは、StepFunctionsの内容を記載しているファイル名、値は、その内容になります。
     *
     * @param rootDirPath StepFunctionsの内容が配置されているルートディレクトリ
     * @return StepFunctionsの内容が設定されたマップ
     * @throws IOException IO例外
     */
    private Map<String, List<String>> loadStepFunctions(String rootDirPath) throws IOException {
        Map<String, List<String>> result = new HashMap<>();
        File rootDir = new File(rootDirPath);
        File[] dirs = rootDir.listFiles();
        if (logger.isDebugEnabled()) {
            logger.debug("dirs.length=" + dirs.length);
        }

        for (File file : dirs) {
            if (file.isDirectory()) {
                throw new CommonException("loadStepFunctions error");
            } else {
                // ファイルの場合
                try (BufferedReader in = new BufferedReader(new FileReader(file))) {
                    List<String> list = new ArrayList<>();
                    while (true) {
                        String line = in.readLine();
                        if (line == null) {
                            break;
                        }
                        list.add(line);
                    }
                    String key = file.getName();
                    result.put(key, list);
                }

            }
        }
        if (logger.isDebugEnabled()) {
            result.entrySet().stream().forEach(entry -> logger.debug(entry.getKey() + ":" + entry.getValue()));
        }

        return result;
    }

    /**
     * テンプレートの行に置換パラメータが含まれている場合、置換パラメータを、StepFunctionsの内容に置き換えます。<br>
     * 置換パラメータがない場合は、末端に改行コードを付加して返却します。
     *
     * @param sfMap StepFunctionsの内容が設定されたマップ
     * @param line テンプレートの行
     * @return テンプレートの行(置換パラメータ置き換え後 or 末端に改行コードを付加)
     */
    private String replaceLine(Map<String, List<String>> sfMap, String line) {
        String lineSeparator = System.getProperty("line.separator");

        String replaceParamPrefix = "%%StepFunctions-File:";
        int index = line.indexOf(replaceParamPrefix);
        if (index < 0) {
            // 置換パラメータが存在しない場合
            return line + lineSeparator;
        }

        int replaceParamStartIndex = index + replaceParamPrefix.length();
        int replaceParamEndIndex = line.substring(replaceParamStartIndex).indexOf("%%");
        if (replaceParamEndIndex < 0) {
            throw new CommonException("step functions file name is not found");
        }

        StringBuilder builder = new StringBuilder();
        String key = line.substring(replaceParamStartIndex, replaceParamStartIndex + replaceParamEndIndex);
        List<String> sfList = sfMap.get(key);
        for (int i = 0; i < sfList.size(); i++) {
            builder.append(createSpace(index)); // スペースで、インデントを付加
            builder.append(sfList.get(i));  // StepFunctionsの内容を追加
            builder.append(lineSeparator);
        }
        return builder.toString();
    }

    /**
     * 引数で指定された数の半角スペースを返却します。
     *
     * @param num 数
     * @return 半角スペース
     */
    private String createSpace(int num) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < num; i++) {
            builder.append(" ");
        }
        return builder.toString();
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.common.base.AbstractClient#finish(boolean, java.lang.Throwable)
     */
    @Override
    protected void finish(boolean isSuccess, Throwable throwable) {
        // 特に実装する内容なし。
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.common.base.AbstractClient#debugArgument()
     */
    @Override
    protected void debugArgument() {
        // 特に実装する内容なし。
    }
}
